import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_provider_demo/commons/log.dart';
import 'package:flutter_provider_demo/commons/result.dart';
import 'package:flutter_provider_demo/commons/security.dart';
import 'package:flutter_provider_demo/data/models/user.dart';
import 'package:flutter_provider_demo/data/services/api/auth_api_client.dart';
import 'package:flutter_provider_demo/data/services/api/models/auth/login_request.dart';
import 'package:flutter_provider_demo/data/services/api/models/auth/login_response.dart';
import 'package:flutter_provider_demo/data/services/secure_storage.dart';
import 'package:flutter_provider_demo/data/services/shared_preference.dart';

class AuthRepository extends ChangeNotifier {
  final AuthApiClient _authApiClient;
  final SecureStorage _secureStorage;
  final SharedPreference _sharedPreference;
  final _log = Log();

  AuthRepository({
    required AuthApiClient authService,
    required SecureStorage secureStorage,
    required SharedPreference sharedPreference,
  })  : _authApiClient = authService,
        _secureStorage = secureStorage,
        _sharedPreference = sharedPreference;

  UserModel? _user;
  String? _accessToken;

  Future<Result<void>> login(String username, String password) async {
    final r = await _authApiClient.login(
      LoginRequest(username: username, password: password),
    );
    if (r is Ok<LoginResponse>) {
      await _saveLoginInfo(r.value);
      return Result.ok(null);
    } else if (r is Error<LoginResponse>) {
      return Result.error(r.error);
    } else {
      return Result.error(Exception("unknown error"));
    }
  }

  Future<Result<void>> logout() async {
    final r1 = await _secureStorage.delete(SecureStorageKey.token);
    if (r1 is Error) {
      _log.error("delete user token error: ${r1.errorMessage()}");
    }
    final r2 = await _sharedPreference.delete(SharedKey.user);
    if (r2 is Error) {
      _log.error("delete user error: ${r2.errorMessage()}");
    }
    _accessToken = '';
    _user = null;
    notifyListeners();
    return Result.ok(null);
  }

  Future<bool> isLogin() async {
    if (_accessToken != null && _accessToken != '') {
      return true;
    } else {
      if (_accessToken == null) {
        await _fetch();
      }
      return _accessToken != '' ? true : false;
    }
  }

  Future<Result<UserModel?>> getUser() async {
    if (_user == null) {
      final r = await _sharedPreference.getJson(SharedKey.user);
      if (r is Ok) {
        final userJson = (r as Ok).value;
        if (userJson != null) {
          _user = UserModel.fromJson(userJson);
        }
      }
    }
    return Result.ok(_user);
  }

  Future<void> checkAccessToken() async {
    if (_accessToken == null) {
      await _fetch();
    }
    if (_accessToken != '') {
      // get jwt token's expire time
      final payload = _accessToken!.split('.')[1];
      // base64 decode
      final json = jsonDecode(SecurityUtils.base64Decode(payload)!);
      final expireTime = (json['exp'] as int) * 1000;
      final now = DateTime.now().millisecondsSinceEpoch;
      if (now > expireTime) {
        final r1 = await _secureStorage.delete(SecureStorageKey.token);
        if (r1 is Error) {
          _log.error(r1.errorMessage());
        }
        final r2 = await _sharedPreference.delete(SharedKey.user);
        if (r2 is Error) {
          _log.error(r2.errorMessage());
        }
        _accessToken = '';
      } else {
        final hour = (expireTime - now) ~/ 1000;
        if (hour < 24) {
          _refreshToken();
        }
      }
    }
  }

  Future<void> _fetch() async {
    final tokenResult = await _secureStorage.get(SecureStorageKey.token);
    if (tokenResult is Ok<String?>) {
      _accessToken = tokenResult.value ?? '';
    } else {
      _accessToken = '';
      _log.info((tokenResult as Error<String?>).errorMessage());
    }
    notifyListeners();
  }

  Future<void> _refreshToken() async {
    final r = await _authApiClient.refreshToken(_accessToken);
    if (r is Ok<LoginResponse>) {
      await _saveLoginInfo(r.value);
    }
  }

  Future<void> _saveLoginInfo(LoginResponse response) async {
    _accessToken = response.accessToken;
    await _secureStorage.save(SecureStorageKey.token, _accessToken!);
    _user = UserModel(
      id: response.userId,
      nickname: response.nickname ?? '',
      avatar: response.avatar,
    );
    await _sharedPreference.setJson(SharedKey.user, _user!.toJson());
    notifyListeners();
  }
}
